package com.ht.modules.authentication.utils;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.springframework.stereotype.Component;

import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;

@Component
@Slf4j
public class RsaUtil {
    /**
     * 加密算法RSA
     */
    private static final String KEY_ALGORITHM = "RSA";
    /**
     * 算法名称/加密模式/数据填充方式
     * 默认：RSA/ECB/PKCS1Padding
     */
    private static final String ALGORITHMS = "RSA/ECB/PKCS1Padding";
    /**
     * RSA最大加密明文大小
     */
    private static final int MAX_ENCRYPT_BLOCK = 245;
    /**
     * RSA最大解密密文大小
     */
    private static final int MAX_DECRYPT_BLOCK = 256;
    /**
     * 1024 117 128
     * RSA 位数 如果采用2048 上面最大加密和最大解密则须填写:  245 256
     */
    private static final int INITIALIZE_LENGTH = 2048;

    /**
     * 生成密钥对(公钥和私钥)
     */
    public static Map<String, Object> genKeyPair(String publicKey, String privateKey)  {
        try {
            log.info("-------------------开始生成密钥对");
            KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
            keyPairGen.initialize(INITIALIZE_LENGTH);
            KeyPair keyPair = keyPairGen.generateKeyPair();
            RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
            RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
            Map<String, Object> keyMap = new HashMap<>(2);
            keyMap.put(publicKey, rsaPublicKey);
            keyMap.put(privateKey, rsaPrivateKey);
            return keyMap;
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }

    public enum Mode {
        PUBLIC_ENCRYPT,
        PUBLIC_DECRYPT,
        PRIVATE_ENCRYPT,
        PRIVATE_DECRYPT;
    }

    /**
     * 私钥加解密
     *
     * @param data   数据
     * @param keyStr 密钥
     */
    public static String decryptByPrivateKey(String data, String keyStr, Mode mode) throws Exception {
        switch (mode) {
            case PUBLIC_ENCRYPT:
                Key PUBLIC_ENCRYPT_KEY = KeyFactory.getInstance(KEY_ALGORITHM).generatePublic(new X509EncodedKeySpec(Base64.decodeBase64(keyStr)));
                Cipher PUBLIC_ENCRYPT_CIPHER = Cipher.getInstance(ALGORITHMS);
                PUBLIC_ENCRYPT_CIPHER.init(Cipher.ENCRYPT_MODE, PUBLIC_ENCRYPT_KEY);
                return Base64.encodeBase64String(encryptAndDecryptOfSubsection(data.getBytes(StandardCharsets.UTF_8), PUBLIC_ENCRYPT_CIPHER, MAX_ENCRYPT_BLOCK));
            case PUBLIC_DECRYPT:
                Key PUBLIC_DECRYPT_KEY = KeyFactory.getInstance(KEY_ALGORITHM).generatePublic(new X509EncodedKeySpec(Base64.decodeBase64(keyStr)));
                Cipher PUBLIC_DECRYPT_CIPHER = Cipher.getInstance(ALGORITHMS);
                PUBLIC_DECRYPT_CIPHER.init(Cipher.DECRYPT_MODE, PUBLIC_DECRYPT_KEY);
                return new String(encryptAndDecryptOfSubsection(Base64.decodeBase64(data), PUBLIC_DECRYPT_CIPHER, MAX_DECRYPT_BLOCK), StandardCharsets.UTF_8);
            case PRIVATE_ENCRYPT:
                Key PRIVATE_ENCRYPT_KEY = KeyFactory.getInstance(KEY_ALGORITHM).generatePrivate(new PKCS8EncodedKeySpec(Base64.decodeBase64(keyStr)));
                Cipher PRIVATE_ENCRYPT_CIPHER = Cipher.getInstance(ALGORITHMS);
                PRIVATE_ENCRYPT_CIPHER.init(Cipher.ENCRYPT_MODE, PRIVATE_ENCRYPT_KEY);
                return Base64.encodeBase64String(encryptAndDecryptOfSubsection(data.getBytes(StandardCharsets.UTF_8), PRIVATE_ENCRYPT_CIPHER, MAX_ENCRYPT_BLOCK));
            case PRIVATE_DECRYPT:
                Key PRIVATE_DECRYPT_KEY = KeyFactory.getInstance(KEY_ALGORITHM).generatePrivate(new PKCS8EncodedKeySpec(Base64.decodeBase64(keyStr)));
                Cipher PRIVATE_DECRYPT_CIPHER = Cipher.getInstance(ALGORITHMS);
                PRIVATE_DECRYPT_CIPHER.init(Cipher.DECRYPT_MODE, PRIVATE_DECRYPT_KEY);
                return new String(encryptAndDecryptOfSubsection(Base64.decodeBase64(data), PRIVATE_DECRYPT_CIPHER, MAX_DECRYPT_BLOCK), StandardCharsets.UTF_8);
            default:
                throw new RuntimeException("未定义的模式");
        }
    }


    /**
     * 获取公私钥
     */
    public static String getPublicPrivateKey(Map<String, Object> KeyPool, String aesKey) {
        Key key = (Key) KeyPool.get(aesKey);
        return Base64.encodeBase64String(key.getEncoded());
    }

    /**
     * 分段进行加密、解密操作
     */
    private static byte[] encryptAndDecryptOfSubsection(byte[] data, Cipher cipher, int encryptBlock) throws Exception {
        int inputLen = data.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;
        // 对数据分段加密
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > encryptBlock) {
                cache = cipher.doFinal(data, offSet, encryptBlock);
            } else {
                cache = cipher.doFinal(data, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * encryptBlock;
        }
        out.close();
        return out.toByteArray();
    }

    /**
     * 用私钥对信息生成数字签名
     *
     * @param data       已加密数据
     * @param privateKey 私钥(BASE64编码)
     */
    public static String sign(String data, String privateKey) throws Exception {
        byte[] keyBytes = Base64.decodeBase64(privateKey);
        PrivateKey privateK = KeyFactory.getInstance(KEY_ALGORITHM).generatePrivate(new PKCS8EncodedKeySpec(keyBytes));
        Signature signature = Signature.getInstance("MD5withRSA");
        signature.initSign(privateK);
        signature.update(Base64.decodeBase64(data));
        return Base64.encodeBase64String(signature.sign());
    }

    /**
     * 校验数字签名
     *
     * @param data      已加密数据
     * @param publicKey 公钥(BASE64编码)
     * @param sign      数字签名
     */
    public static boolean verify(String data, String publicKey, String sign) throws Exception {
        byte[] keyBytes = Base64.decodeBase64(publicKey);
        PublicKey publicK = KeyFactory.getInstance(KEY_ALGORITHM).generatePublic(new X509EncodedKeySpec(keyBytes));
        Signature signature = Signature.getInstance("MD5withRSA");
        signature.initVerify(publicK);
        signature.update(Base64.decodeBase64(data));
        return signature.verify(Base64.decodeBase64(sign));
    }

    public static void main(String[] args) {
        try {
            String publicAesKey = AesUtil.getKey();
            System.out.println("publicAesKey = " + publicAesKey);
            String privateAesKey = AesUtil.getKey();
            System.out.println("privateAesKey = " + privateAesKey);
            Map<String, Object> keys = genKeyPair(publicAesKey, privateAesKey);
            String pubKey = getPublicPrivateKey(keys, publicAesKey);
            System.out.println("pubKey = " + pubKey);
            String priKey = getPublicPrivateKey(keys, privateAesKey);
            System.out.println("priKey = " + priKey);
            String content = "这是我要加密的内容...";
            System.out.println("加密内容： " + content);
            // 私钥加密
            String string1 = decryptByPrivateKey(content, priKey, Mode.PRIVATE_ENCRYPT);
            System.out.println("私钥加密 = " + string1);
            // 公钥解密
            String string2 = decryptByPrivateKey(string1, pubKey, Mode.PUBLIC_DECRYPT);
            System.out.println("公钥解密 = " + string2);
            // 公钥加密
            String string3 = decryptByPrivateKey("9d71cbdaed90874db5301817e855f4f4", "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApaT9zZQWw7u84qilqm1PqemkxvB0W2bWMGsj2AdI+IvUWdQfPePXZjJ6ONbw6QW2Yg6VbcDvKRvi+893fYhj1koZSQfoZUcSVxvkO3yiqgNf+qolLsT944d3saKAWQ51jmB2PqEW1u6xwl+jXX9A5AjYY1qe6HrKaeXi7vbscpeb6taDPcQWFP25JTJf3oYgCmfhLITbHU8jOL+iZAH+FjTbiU3OSEs4+sFTekAr+DeFMFe75ZCjcWrAxPQ533si3QAPDmohRvwjP7zYmPfyHN6spDmO0ry+Oz/gRBJw3wY5iF71p3qsQsz9iIBqQJr0F2+Jh/pWjiimj7/In3iSVQIDAQAB", Mode.PUBLIC_ENCRYPT);
            System.out.println("公钥加密 = " + string3);
            // 私钥解密
            String string4 = decryptByPrivateKey(string3, "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQClpP3NlBbDu7ziqKWqbU+p6aTG8HRbZtYwayPYB0j4i9RZ1B8949dmMno41vDpBbZiDpVtwO8pG+L7z3d9iGPWShlJB+hlRxJXG+Q7fKKqA1/6qiUuxP3jh3exooBZDnWOYHY+oRbW7rHCX6Ndf0DkCNhjWp7oespp5eLu9uxyl5vq1oM9xBYU/bklMl/ehiAKZ+EshNsdTyM4v6JkAf4WNNuJTc5ISzj6wVN6QCv4N4UwV7vlkKNxasDE9DnfeyLdAA8OaiFG/CM/vNiY9/Ic3qykOY7SvL47P+BEEnDfBjmIXvWneqxCzP2IgGpAmvQXb4mH+laOKKaPv8ifeJJVAgMBAAECggEAI4xw5ptrNkb2XrtcUGLEt72bfhFRG2OKwJnht6U6mRKjOEMAkG7zBWz8U3H3gj7WWR1xnrwK54rjT/DCn1dzu6Or4QM4MfIKgtTKyqRxIWT3hmtY7vxaU2f5fU8XVeV3P56+iarXJLnVvtX5EKH0emvWip4CX/ioM4lhRjEKZf4AG34FVUItD3KOLGwrl3Fq8mZuRjjXqaYh1num1RJBlCTlfx8H/tipBp0K3+wbEQgacEY5j2ZA4LL8ooMRcwFzbBk1iBgorx+lkAz5YgXjkuyKiAaIMim8TKdOe1fB73ojp8xdXOnEbHCbKvSU9UKyakJr+SgxmVbji7wqwASi2QKBgQC5kG5+2prTdL/fhdjGQXI+FJyd1LWi54lq0wjwWAK3eG6Gxt7hRzu3kJnYr2Tpy3P0JeSeRxGsAoZ81vmTTQKEYT8GnP9sGpGcS8WV3goXCCFBX/TiIwi4Cj9B4M9+bdF6+xSYpZpJtpMvTuCgnuX5iPwlvun7TvC7m+Zv40EmuQKBgQDkhO6Ok9BO4LyTeDg0XGcknOeMs5WBLh4nFny0SBaB4wlcN55qgFrNlBuFftxjHyaQs6cWsPEqwNU3OGyaIUu9bf7vHC4dQiSJ8+D0tZmohUOlo0Ue+rQxd7MlXhoAjsqQM+4AUfAo2FppYggVf58ri/JKiwHdBMqu8cLU45j6fQKBgQCWofgQYcRtAsj+2dgul9H1MyYvuQv5QZr7bHRFJiyOYuTV43npxblkOa2piDeS0eU+u8Pp+CcQSryBesD+IfV5ILfY1nlBeA/boGa0UZo/OApG124iY+4f97GB+Mx3BQ6XlloRz9x/GgZA0PbnbBWjit+khGXL6zjgEp3tFb09gQKBgGD6GNLg37HZpa1fg1qVLCBZhdosC/I6RAmKfXl0bjjIxJwh7lR8DAzaIaPRZm2aNp+J3gq/JFlkZBd/FneCUcYd++Y1K0y3WRJCMOEogAEILOEKHLc85WNozNm6yYYbEtOHdiUkoKp0xkmalJurAT1Vsoj33N6SvgDoECqQO8dxAoGAbh/BYyhlJCIbgBJV/jIl54ucGjb9d3kOzvVdje9ibs6WkcOAnhm44+yorc7PWHgCalLKCdFgLXFRW3BxB7xZyfwFHaDkB9PucvQ3ZzGFh/N3k7kbOl99/X3kPgIyOMlo1Cx4lswK/aLB6SozVq/GKL6UsPc/l6y30MhKdVri/CU=", Mode.PRIVATE_DECRYPT);
            System.out.println("私钥解密 = " + string4);
            // 生成数字签名
            String sign = sign(string1, priKey);
            System.out.println("sign = " + sign);
            boolean verify = verify(string1, pubKey, sign);
            System.out.println("verify = " + verify);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
